 package ain.standard;

import java.util.ArrayList;

import ain.Antibody;

import util.Vector;

import environment.Ball;
import environment.Pitch;
import environment.Team;
import environment.player.Player;

public abstract class StandardAbstractAntibody implements Antibody
{
	// fields used for the ball-robot affinity
	private double[][] pitchZones;
	private double zoneHeight, zoneWidth;
	
	protected Pitch pitch;
	protected Player player;
	protected double antigenAffinity;
	
	protected double concentrationChangeValue, concentration;
	
	protected static double FAR_RADIUS = 0;
	protected static double NEAR_RADIUS = FAR_RADIUS / 2;
	
	public StandardAbstractAntibody(Player player, Pitch pitch)
	{
		this.pitch = pitch;
		this.player = player;
		preparePitchZones(player.getTeam());
		
		FAR_RADIUS = pitch.getWidth()/4;
	}
	
	public void antigenAffinity()
	{
		//reset old affinity value with antigens
		antigenAffinity = 0;
		
		//calculate affinity with antigens
		double distBallGoal = affinityBallGoal();
		double distBallPlayer = affinityBallPlayer();
		double environmentState = distBallRatioAffinity();
				
		antigenAffinity = (distBallGoal + distBallPlayer + distBallRatioAffinity()) / 3;
//		antigenAffinity = (distBallGoal + environmentState) / 2.0;
//		antigenAffinity = (distBallGoal + distBallPlayer) / 2.0;
	}
	
	private double distBallRatioAffinity()
	{
		// ratio between self distance to the ball and minimal distance to the ball from other team
		// 1. find min distance from other team
		// 2. find value for each antibody. take [0, 2] as reference interval
		
		double minDst = Double.MAX_VALUE;
		for (Player p : pitch.getPlayers())
		{
			if (!p.getTeam().equals(player.getTeam()))
			{
				double distance = Vector.substract(p.getPosition(), pitch.getBall().getPosition()).magnitude();
				minDst = distance < minDst ? distance : minDst;
			}
		}
		
//		double ratio = Vector.substract(player.getPosition(), pitch.getBall().getPosition()).magnitude() / minDst;
		double ratio = minDst / player.getPosition().euclDistance(pitch.getBall().getPosition());
		
		if (ratio < 0) System.out.println("distBallRatioAffinity WARNING - negative value");
		
		return distBallRatioValue(ratio);
	}
	
	abstract protected double distBallRatioValue(double ratio);
	
	private double affinityBallGoal()
	{
		// find the current zone of the player & return affinity value of that zone
		int row = (int) (pitch.getBall().getPosition().getY() / zoneHeight);
		int column = (int) (pitch.getBall().getPosition().getX() / zoneWidth);
		
		return distBallGoalValue(pitchZones[row][column]);
//		return imprDistBallGoalValue(pitchZones[row][column]);
	}
	
	abstract protected double distBallGoalValue(double zoneNumber);
	abstract protected double imprDistBallGoalValue(double zoneNumber);
	
	private double affinityBallPlayer()
	{
		double distance = pitch.getBall().getPosition().euclDistance(player.getPosition());
		
		return distBallPlayerValue(distance);
	}
	
	abstract protected double distBallPlayerValue(double distance);
	
	private void preparePitchZones(Team team)
	{
		// the pitch will be divided into 24 Zones: 4 rows with 6 columns each
		// this is a hard-coded partition taken from the paper
		if (team.equals(Team.HOME))
		{
			double[][] zones = {	{3, 3, 2, 2, 1, 1},
						{5, 4, 3, 2, 1, 1},
						{5, 4, 3, 2, 1, 1},
						{3, 3, 2, 2, 1, 1}};
			pitchZones =  zones;
		}
		else
		{
			double[][] zones = {	{1, 1, 2, 2, 3, 3},
						{1, 1, 2, 3, 4, 5},
						{1, 1, 2, 3, 4, 5},
						{1, 1, 2, 2, 3, 3}};
			
			pitchZones = zones;
		}
		
		// calculate the height and width of a zone. this is needed for the ball-robot affinity
		zoneHeight = pitch.getHeight() / pitchZones.length;
		zoneWidth = pitch.getWidth() / pitchZones[0].length;
	}
	
	public void concentrationChange()
	{
		concentration = 1 / (1 + Math.exp(0.5 - concentrationChangeValue));
		if (concentration < 0)
		{
			System.out.println("WARNING - AA: concentration negative: " + toString() + " " + concentration);
			concentration = 0;
		}
		concentrationChangeValue = 0;
	}
	
	public void setConcentrationChange(double value)
	{
		concentrationChangeValue = value;
	}
	
	public double getConcentration()
	{		
		return concentration;
	}
	
	public double getAntigenAffinity()
	{
		return antigenAffinity;
	}
	
	public double[][] getPitchZones()
	{
		return  pitchZones;
	}
}
